{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Langchain4j LLM streaming"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "String userHomeDir = System.getProperty(\"user.home\");\n",
    "String localRespoUrl = \"file://\" + userHomeDir + \"/.m2/repository/\";\n",
    "String langchain4jVersion = \"0.36.2\";\n",
    "String langgraph4jVersion = \"1.4-SNAPSHOT\";"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Remove installed package from Jupiter cache"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%bash \n",
    "rm -rf \\{userHomeDir}/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/bsc/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[0mRepository \u001b[1m\u001b[32mlocal\u001b[0m url: \u001b[1m\u001b[32mfile:///Users/bsorrentino/.m2/repository/\u001b[0m added.\n",
      "\u001b[0mAdding dependency \u001b[0m\u001b[1m\u001b[32morg.slf4j:slf4j-jdk14:2.0.9\n",
      "\u001b[0mAdding dependency \u001b[0m\u001b[1m\u001b[32morg.bsc.langgraph4j:langgraph4j-core:1.3-SNAPSHOT\n",
      "\u001b[0mAdding dependency \u001b[0m\u001b[1m\u001b[32morg.bsc.langgraph4j:langgraph4j-langchain4j:1.3-SNAPSHOT\n",
      "\u001b[0mAdding dependency \u001b[0m\u001b[1m\u001b[32mdev.langchain4j:langchain4j:0.36.2\n",
      "\u001b[0mAdding dependency \u001b[0m\u001b[1m\u001b[32mdev.langchain4j:langchain4j-open-ai:0.36.2\n",
      "\u001b[0mSolving dependencies\n",
      "Resolved artifacts count: 26\n",
      "Add to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/slf4j/slf4j-jdk14/2.0.9/slf4j-jdk14-2.0.9.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/slf4j/slf4j-api/2.0.9/slf4j-api-2.0.9.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/bsc/langgraph4j/langgraph4j-core/1.3-SNAPSHOT/langgraph4j-core-1.3-SNAPSHOT.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/bsc/async/async-generator/3.0.0/async-generator-3.0.0.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/bsc/langgraph4j/langgraph4j-langchain4j/1.3-SNAPSHOT/langgraph4j-langchain4j-1.3-SNAPSHOT.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/dev/langchain4j/langchain4j/0.36.2/langchain4j-0.36.2.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/dev/langchain4j/langchain4j-core/0.36.2/langchain4j-core-0.36.2.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/com/google/code/gson/gson/2.10.1/gson-2.10.1.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/apache/opennlp/opennlp-tools/1.9.4/opennlp-tools-1.9.4.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/dev/langchain4j/langchain4j-open-ai/0.36.2/langchain4j-open-ai-0.36.2.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/dev/ai4j/openai4j/0.23.0/openai4j-0.23.0.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/com/squareup/retrofit2/retrofit/2.9.0/retrofit-2.9.0.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/com/squareup/retrofit2/converter-jackson/2.9.0/converter-jackson-2.9.0.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/com/fasterxml/jackson/core/jackson-databind/2.17.2/jackson-databind-2.17.2.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/com/fasterxml/jackson/core/jackson-annotations/2.17.2/jackson-annotations-2.17.2.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/com/fasterxml/jackson/core/jackson-core/2.17.2/jackson-core-2.17.2.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/com/squareup/okhttp3/okhttp/4.12.0/okhttp-4.12.0.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/com/squareup/okio/okio/3.6.0/okio-3.6.0.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/com/squareup/okio/okio-jvm/3.6.0/okio-jvm-3.6.0.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/jetbrains/kotlin/kotlin-stdlib-common/1.9.10/kotlin-stdlib-common-1.9.10.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/com/squareup/okhttp3/okhttp-sse/4.12.0/okhttp-sse-4.12.0.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.25/kotlin-stdlib-jdk8-1.9.25.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/jetbrains/kotlin/kotlin-stdlib/1.9.25/kotlin-stdlib-1.9.25.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/jetbrains/annotations/13.0/annotations-13.0.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.25/kotlin-stdlib-jdk7-1.9.25.jar\u001b[0m\n",
      "\u001b[0mAdd to classpath: \u001b[0m\u001b[32m/Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/com/knuddels/jtokkit/1.1.0/jtokkit-1.1.0.jar\u001b[0m\n",
      "\u001b[0m"
     ]
    }
   ],
   "source": [
    "%dependency /add-repo local \\{localRespoUrl} release|never snapshot|always\n",
    "// %dependency /list-repos\n",
    "%dependency /add org.slf4j:slf4j-jdk14:2.0.9\n",
    "%dependency /add org.bsc.langgraph4j:langgraph4j-core:\\{langgraph4jVersion}\n",
    "%dependency /add org.bsc.langgraph4j:langgraph4j-langchain4j:\\{langgraph4jVersion}\n",
    "%dependency /add dev.langchain4j:langchain4j:\\{langchain4jVersion}\n",
    "%dependency /add dev.langchain4j:langchain4j-open-ai:\\{langchain4jVersion}\n",
    "\n",
    "%dependency /resolve"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Initialize Logger**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "try( var file = new java.io.FileInputStream(\"./logging.properties\")) {\n",
    "    var lm = java.util.logging.LogManager.getLogManager();\n",
    "    lm.checkAccess(); \n",
    "    lm.readConfiguration( file );\n",
    "}\n",
    "\n",
    "var log = org.slf4j.LoggerFactory.getLogger(\"llm-streaming\");\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## How to use LLMStreamingGenerator"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "StreamingOutput{chunk=} \n",
      "StreamingOutput{chunk=Why} \n",
      "StreamingOutput{chunk= did} \n",
      "StreamingOutput{chunk= the} \n",
      "StreamingOutput{chunk= scare} \n",
      "StreamingOutput{chunk=crow} \n",
      "StreamingOutput{chunk= win} \n",
      "StreamingOutput{chunk= an} \n",
      "StreamingOutput{chunk= award} \n",
      "StreamingOutput{chunk=?\n",
      "\n",
      "} \n",
      "StreamingOutput{chunk=Because} \n",
      "StreamingOutput{chunk= he} \n",
      "StreamingOutput{chunk= was} \n",
      "StreamingOutput{chunk= outstanding} \n",
      "StreamingOutput{chunk= in} \n",
      "StreamingOutput{chunk= his} \n",
      "StreamingOutput{chunk= field} \n",
      "StreamingOutput{chunk=!} \n",
      "RESULT: {content=AiMessage { text = \"Why did the scarecrow win an award?\n",
      "\n",
      "Because he was outstanding in his field!\" toolExecutionRequests = null }} \n"
     ]
    }
   ],
   "source": [
    "import dev.langchain4j.model.StreamingResponseHandler;\n",
    "import dev.langchain4j.model.chat.StreamingChatLanguageModel;\n",
    "import dev.langchain4j.model.openai.OpenAiStreamingChatModel;\n",
    "import dev.langchain4j.data.message.AiMessage;\n",
    "import dev.langchain4j.model.output.Response;\n",
    "import static dev.langchain4j.model.openai.OpenAiChatModelName.GPT_4_O_MINI;\n",
    "import org.bsc.langgraph4j.langchain4j.generators.LLMStreamingGenerator;\n",
    "import org.bsc.langgraph4j.state.AgentState;\n",
    "import org.bsc.langgraph4j.streaming.StreamingOutput;\n",
    "\n",
    "var generator = LLMStreamingGenerator.<AiMessage,AgentState>builder()\n",
    "                        .mapResult( r -> Map.of( \"content\", r.content() ) )\n",
    "                        .build();\n",
    "\n",
    "StreamingChatLanguageModel model = OpenAiStreamingChatModel.builder()\n",
    "    .apiKey(System.getenv(\"OPENAI_API_KEY\"))\n",
    "    .modelName(GPT_4_O_MINI)\n",
    "    .build();\n",
    "\n",
    "String userMessage = \"Tell me a joke\";\n",
    "\n",
    "model.generate(userMessage, generator.handler() );\n",
    "\n",
    "for( var r : generator ) {\n",
    "    log.info( \"{}\", r);\n",
    "}\n",
    "  \n",
    "log.info( \"RESULT: {}\", generator.resultValue().orElse(null) );\n",
    "  \n",
    "//Thread.sleep( 1000 );"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Use LLMStreamGenerator in Agent"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Define Serializers"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "SerializerMapper: \n",
       "java.util.Map\n",
       "java.util.Collection\n",
       "dev.langchain4j.agent.tool.ToolExecutionRequest\n",
       "dev.langchain4j.data.message.ChatMessage"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import dev.langchain4j.data.message.ChatMessage;\n",
    "import dev.langchain4j.data.message.AiMessage;\n",
    "import dev.langchain4j.data.message.SystemMessage;\n",
    "import dev.langchain4j.data.message.UserMessage;\n",
    "import dev.langchain4j.data.message.ToolExecutionResultMessage;\n",
    "import dev.langchain4j.agent.tool.ToolExecutionRequest;\n",
    "import org.bsc.langgraph4j.serializer.std.ObjectStreamStateSerializer;\n",
    "import org.bsc.langgraph4j.langchain4j.serializer.std.ChatMesssageSerializer;\n",
    "import org.bsc.langgraph4j.langchain4j.serializer.std.ToolExecutionRequestSerializer;\n",
    "import org.bsc.langgraph4j.state.AgentStateFactory;\n",
    "import org.bsc.langgraph4j.prebuilt.MessagesState;\n",
    "\n",
    "var stateSerializer = new ObjectStreamStateSerializer<MessagesState<ChatMessage>>( MessagesState::new );\n",
    "stateSerializer.mapper()\n",
    "    // Setup custom serializer for Langchain4j ToolExecutionRequest\n",
    "    .register(ToolExecutionRequest.class, new ToolExecutionRequestSerializer() )\n",
    "    // Setup custom serializer for Langchain4j AiMessage\n",
    "    .register(ChatMessage.class, new ChatMesssageSerializer() );\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Set up the tools\n",
    "\n",
    "Using [langchain4j], We will first define the tools we want to use. For this simple example, we will\n",
    "use create a placeholder search engine. However, it is really easy to create\n",
    "your own tools - see documentation\n",
    "[here][tools] on how to do\n",
    "that.\n",
    "\n",
    "[langchain4j]: https://docs.langchain4j.dev\n",
    "[tools]: https://docs.langchain4j.dev/tutorials/tools"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "import dev.langchain4j.agent.tool.P;\n",
    "import dev.langchain4j.agent.tool.Tool;\n",
    "\n",
    "import java.util.Optional;\n",
    "\n",
    "import static java.lang.String.format;\n",
    "\n",
    "public class SearchTool {\n",
    "\n",
    "    @Tool(\"Use to surf the web, fetch current information, check the weather, and retrieve other information.\")\n",
    "    String execQuery(@P(\"The query to use in your search.\") String query) {\n",
    "\n",
    "        // This is a placeholder for the actual implementation\n",
    "        return \"Cold, with a low of 13 degrees\";\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "import static org.bsc.langgraph4j.StateGraph.START;\n",
    "import static org.bsc.langgraph4j.StateGraph.END;\n",
    "import org.bsc.langgraph4j.prebuilt.MessagesStateGraph;\n",
    "import org.bsc.langgraph4j.action.EdgeAction;\n",
    "import static org.bsc.langgraph4j.action.AsyncEdgeAction.edge_async;\n",
    "import org.bsc.langgraph4j.action.NodeAction;\n",
    "import static org.bsc.langgraph4j.action.AsyncNodeAction.node_async;\n",
    "import dev.langchain4j.service.tool.DefaultToolExecutor;\n",
    "import org.bsc.langgraph4j.langchain4j.tool.ToolNode;\n",
    "import dev.langchain4j.agent.tool.ToolSpecification;\n",
    "import dev.langchain4j.model.openai.OpenAiStreamingChatModel;\n",
    "\n",
    "\n",
    "// setup streaming model\n",
    "var model = OpenAiStreamingChatModel.builder()\n",
    "  .apiKey( System.getenv(\"OPENAI_API_KEY\") )\n",
    "  .modelName( \"gpt-4o-mini\" )\n",
    "  .logResponses(true)\n",
    "  .temperature(0.0)\n",
    "  .maxTokens(2000)\n",
    "  .build();\n",
    "\n",
    "// setup tools \n",
    "var tools = ToolNode.builder()\n",
    "              .specification( new SearchTool() ) \n",
    "              .build(); \n",
    "\n",
    "NodeAction<MessagesState<ChatMessage>> callModel = state -> {\n",
    "  log.info(\"CallModel:\\n{}\", state.messages());\n",
    "\n",
    "  var generator = LLMStreamingGenerator.<AiMessage, MessagesState<ChatMessage>>builder()\n",
    "          .mapResult(response -> {\n",
    "              log.info(\"MapResult: {}\", response);\n",
    "              return Map.of(\"messages\", response.content());\n",
    "          })\n",
    "          .startingNode(\"agent\")\n",
    "          .startingState(state)\n",
    "          .build();\n",
    "\n",
    "    model.generate(\n",
    "            state.messages(),\n",
    "            tools.toolSpecifications(),\n",
    "            generator.handler());\n",
    "\n",
    "    return Map.of(\"messages\", generator);\n",
    "};\n",
    "            \n",
    "// Route Message\n",
    "EdgeAction<MessagesState<ChatMessage>> routeMessage = state -> {\n",
    "    log.info(\"routeMessage:\\n{}\", state.messages());\n",
    "\n",
    "    var lastMessage = state.lastMessage()\n",
    "            .orElseThrow(() -> (new IllegalStateException(\"last message not found!\")));\n",
    "\n",
    "    if (lastMessage instanceof AiMessage message) {\n",
    "        // If tools should be called\n",
    "        if (message.hasToolExecutionRequests()) return \"next\";\n",
    "    }\n",
    "\n",
    "    // If no tools are called, we can finish (respond to the user)\n",
    "    return \"exit\";\n",
    "};\n",
    "            \n",
    "// Invoke Tool\n",
    "NodeAction<MessagesState<ChatMessage>> invokeTool = state -> {\n",
    "    log.info(\"invokeTool:\\n{}\", state.messages());\n",
    "\n",
    "    var lastMessage = state.lastMessage()\n",
    "            .orElseThrow(() -> (new IllegalStateException(\"last message not found!\")));\n",
    "\n",
    "\n",
    "    if (lastMessage instanceof AiMessage lastAiMessage) {\n",
    "\n",
    "        var result = tools.execute(lastAiMessage.toolExecutionRequests(), null)\n",
    "                .orElseThrow(() -> (new IllegalStateException(\"no tool found!\")));\n",
    "\n",
    "        return Map.of(\"messages\", result);\n",
    "\n",
    "    }\n",
    "\n",
    "    throw new IllegalStateException(\"invalid last message\");\n",
    "};\n",
    "            \n",
    "// Define Graph\n",
    "var workflow = new MessagesStateGraph<ChatMessage>(stateSerializer)\n",
    "        .addNode(\"agent\", node_async(callModel))\n",
    "        .addNode(\"tools\", node_async(invokeTool))\n",
    "        .addEdge(START, \"agent\")\n",
    "        .addConditionalEdges(\"agent\",\n",
    "                edge_async(routeMessage),\n",
    "                Map.of(\"next\", \"tools\", \"exit\", END))\n",
    "        .addEdge(\"tools\", \"agent\");\n",
    "            \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "START \n",
      "CallModel:\n",
      "[UserMessage { name = null contents = [TextContent { text = \"what is the whether today?\" }] }] \n",
      "MapResult: Response { content = AiMessage { text = null toolExecutionRequests = [ToolExecutionRequest { id = \"call_VSMGzPIUc51ZkdtXHsNK3Ekp\", name = \"execQuery\", arguments = \"{\"query\":\"current weather\"}\" }] }, tokenUsage = TokenUsage { inputTokenCount = 71, outputTokenCount = 16, totalTokenCount = 87 }, finishReason = TOOL_EXECUTION, metadata = {} } \n",
      "routeMessage:\n",
      "[AiMessage { text = null toolExecutionRequests = [ToolExecutionRequest { id = \"call_VSMGzPIUc51ZkdtXHsNK3Ekp\", name = \"execQuery\", arguments = \"{\"query\":\"current weather\"}\" }] }] \n",
      "NodeOutput{node=__START__, state={messages=[UserMessage { name = null contents = [TextContent { text = \"what is the whether today?\" }] }]}} \n",
      "invokeTool:\n",
      "[AiMessage { text = null toolExecutionRequests = [ToolExecutionRequest { id = \"call_VSMGzPIUc51ZkdtXHsNK3Ekp\", name = \"execQuery\", arguments = \"{\"query\":\"current weather\"}\" }] }] \n",
      "execute: execQuery \n",
      "NodeOutput{node=agent, state={messages=[AiMessage { text = null toolExecutionRequests = [ToolExecutionRequest { id = \"call_VSMGzPIUc51ZkdtXHsNK3Ekp\", name = \"execQuery\", arguments = \"{\"query\":\"current weather\"}\" }] }]}} \n",
      "CallModel:\n",
      "[AiMessage { text = null toolExecutionRequests = [ToolExecutionRequest { id = \"call_VSMGzPIUc51ZkdtXHsNK3Ekp\", name = \"execQuery\", arguments = \"{\"query\":\"current weather\"}\" }] }, ToolExecutionResultMessage { id = \"call_VSMGzPIUc51ZkdtXHsNK3Ekp\" toolName = \"execQuery\" text = \"Cold, with a low of 13 degrees\" }] \n",
      "NodeOutput{node=tools, state={messages=[AiMessage { text = null toolExecutionRequests = [ToolExecutionRequest { id = \"call_VSMGzPIUc51ZkdtXHsNK3Ekp\", name = \"execQuery\", arguments = \"{\"query\":\"current weather\"}\" }] }, ToolExecutionResultMessage { id = \"call_VSMGzPIUc51ZkdtXHsNK3Ekp\" toolName = \"execQuery\" text = \"Cold, with a low of 13 degrees\" }]}} \n",
      "StreamingOutput{node=agent, chunk= } \n",
      "StreamingOutput{node=agent, chunk=The } \n",
      "StreamingOutput{node=agent, chunk= current } \n",
      "StreamingOutput{node=agent, chunk= weather } \n",
      "StreamingOutput{node=agent, chunk= is } \n",
      "StreamingOutput{node=agent, chunk= cold } \n",
      "StreamingOutput{node=agent, chunk=, } \n",
      "StreamingOutput{node=agent, chunk= with } \n",
      "StreamingOutput{node=agent, chunk= a } \n",
      "StreamingOutput{node=agent, chunk= low } \n",
      "StreamingOutput{node=agent, chunk= of } \n",
      "StreamingOutput{node=agent, chunk=  } \n",
      "StreamingOutput{node=agent, chunk=13 } \n",
      "StreamingOutput{node=agent, chunk= degrees } \n",
      "StreamingOutput{node=agent, chunk=. } \n",
      "StreamingOutput{node=agent, chunk= If } \n",
      "StreamingOutput{node=agent, chunk= you } \n",
      "StreamingOutput{node=agent, chunk= need } \n",
      "StreamingOutput{node=agent, chunk= more } \n",
      "StreamingOutput{node=agent, chunk= specific } \n",
      "StreamingOutput{node=agent, chunk= information } \n",
      "StreamingOutput{node=agent, chunk= or } \n",
      "StreamingOutput{node=agent, chunk= details } \n",
      "StreamingOutput{node=agent, chunk= about } \n",
      "StreamingOutput{node=agent, chunk= a } \n",
      "StreamingOutput{node=agent, chunk= particular } \n",
      "StreamingOutput{node=agent, chunk= location } \n",
      "StreamingOutput{node=agent, chunk=, } \n",
      "StreamingOutput{node=agent, chunk= feel } \n",
      "StreamingOutput{node=agent, chunk= free } \n",
      "StreamingOutput{node=agent, chunk= to } \n",
      "StreamingOutput{node=agent, chunk= ask } \n",
      "MapResult: Response { content = AiMessage { text = \"The current weather is cold, with a low of 13 degrees. If you need more specific information or details about a particular location, feel free to ask!\" toolExecutionRequests = null }, tokenUsage = TokenUsage { inputTokenCount = 93, outputTokenCount = 34, totalTokenCount = 127 }, finishReason = STOP, metadata = {} } \n",
      "routeMessage:\n",
      "[AiMessage { text = \"The current weather is cold, with a low of 13 degrees. If you need more specific information or details about a particular location, feel free to ask!\" toolExecutionRequests = null }] \n",
      "StreamingOutput{node=agent, chunk=! } \n",
      "NodeOutput{node=agent, state={messages=[AiMessage { text = \"The current weather is cold, with a low of 13 degrees. If you need more specific information or details about a particular location, feel free to ask!\" toolExecutionRequests = null }]}} \n",
      "NodeOutput{node=__END__, state={messages=[AiMessage { text = \"The current weather is cold, with a low of 13 degrees. If you need more specific information or details about a particular location, feel free to ask!\" toolExecutionRequests = null }]}} \n"
     ]
    }
   ],
   "source": [
    "import org.bsc.langgraph4j.streaming.StreamingOutput;\n",
    "\n",
    "var app = workflow.compile();\n",
    "\n",
    "for( var out : app.stream( Map.of( \"messages\", UserMessage.from( \"what is the whether today?\")) ) ) {\n",
    "  if( out instanceof StreamingOutput streaming ) {\n",
    "    log.info( \"StreamingOutput{node={}, chunk={} }\", streaming.node(), streaming.chunk() );\n",
    "  }\n",
    "  else {\n",
    "    log.info( \"{}\", out );\n",
    "  }\n",
    "\n",
    "}\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Java (rjk 2.2.0)",
   "language": "java",
   "name": "rapaio-jupyter-kernel"
  },
  "language_info": {
   "codemirror_mode": "java",
   "file_extension": ".jshell",
   "mimetype": "text/x-java-source",
   "name": "java",
   "nbconvert_exporter": "script",
   "pygments_lexer": "java",
   "version": "17.0.2+8-86"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
